using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.Diagnostics;

namespace KodeIT.Web
{
    internal class ImpersonationContext : IDisposable
    {
        #region Constants

        private const int TOKEN_READ = 0x00020008;
        private const int TOKEN_IMPERSONATE = 0x0004;
        private const int ERROR_NO_TOKEN = 1008;

        #endregion

        #region External Declarations

        [DllImport("kernel32.dll", CharSet = CharSet.Ansi, SetLastError = true)]
        private static extern IntPtr GetCurrentThread();

        [DllImport("advapi32.dll", CharSet = CharSet.Ansi, SetLastError = true)]
        private static extern bool OpenThreadToken(IntPtr ThreadHandle, uint DesiredAccess, bool OpenAsSelf, out IntPtr TokenHandle);

        [DllImport("advapi32.dll", CharSet = CharSet.Ansi, SetLastError = true)]
        private static extern bool SetThreadToken(IntPtr pHandle, IntPtr hToken);

        [DllImport("advapi32.dll", CharSet = CharSet.Ansi, SetLastError = true)]
        private static extern bool RevertToSelf();

        [DllImport("kernel32.dll", CharSet = CharSet.Ansi, SetLastError = true)]
        private static extern bool CloseHandle(IntPtr handle);

        #endregion

        #region Variables

        private IntPtr hToken = IntPtr.Zero;

        #endregion

        internal ImpersonationContext()
        {
            if (!OpenThreadToken(GetCurrentThread(), TOKEN_READ | TOKEN_IMPERSONATE, true, out hToken))
            {
                int lastWinError = Marshal.GetLastWin32Error();
                if (ERROR_NO_TOKEN != lastWinError)
                {
                    Trace.WriteLine("Unexpected error 0x" + lastWinError.ToString("x4") + " on OpenThreadToken", "Impersonation");
                }

                hToken = IntPtr.Zero;
            }
            else
            {
                if (!RevertToSelf())
                {
                    int lastWinError = Marshal.GetLastWin32Error();
                    Trace.WriteLine("Failed to revert!, error 0x" + lastWinError.ToString("x4"), "Impersonation");
                }
            }
        }

        void IDisposable.Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (IntPtr.Zero != hToken)
                {
                    if (!SetThreadToken(IntPtr.Zero, hToken))
                    {
                        int lastWinError = Marshal.GetLastWin32Error();
                        Trace.WriteLine("Could not reset cloak!, error 0x" + lastWinError.ToString("x4"), "Impersonation");
                    }
                }

            }

            if (IntPtr.Zero != hToken)
            {
                if (!CloseHandle(hToken))
                {
                    int lastWinError = Marshal.GetLastWin32Error();
                    Trace.WriteLine("Could not close cloak token!, error 0x" + lastWinError.ToString("x4"), "Impersonation"); ;
                }
                hToken = IntPtr.Zero;
            }
        }

        ~ImpersonationContext()
        {
            Dispose(false);
        }
    }
}
